/**
 *Copyright [2010] [lcj(caijunlu@gmail.com)]
 *Licensed under the Apache License, Version 2.0 (the "License");
 *you may not use this file except in compliance with the License. 
 *You may obtain a copy of the License at 
 *             http://www.apache.org/licenses/LICENSE-2.0 
 *Unless required by applicable law or agreed to in writing, 
 *software distributed under the License is distributed on an "AS IS" BASIS, 
 *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 *either express or implied. See the License for the specific language governing permissions and limitations under the License
 */
package org.independent.servlet.session.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.independent.servlet.session.service.ContextHolderFactory;

/**
 * get the object's all classes include the field object include all classes
 * 
 * @author lcj
 * 
 */
public class ObjectClasses {

	/**
	 * to cached the java base data types
	 */
	private static Map<String, Class<?>> baseDataType = new HashMap<String, Class<?>>();


	/**
	 * get the object include all the classes
	 * 
	 * @param o
	 *            the object
	 * @return a list has stored classes
	 */
	public static ArrayList<Class<?>> getClassesByObject(Object o) {
		Class<?> objectClazz = o.getClass();
		return getClassesByClass(objectClazz, o);
	}

	/**
	 * if the object is collection
	 * 
	 * @param owner
	 * @param methodName
	 * @param args
	 * @return
	 * @throws Exception
	 */
	private static Object invokeMethod(Object owner, String methodName,
			Object[] args) throws Exception {
		Class<?> ownerClass = owner.getClass();
		Method method = null;
		try {
			method = ownerClass.getMethod("get"
					+ methodName.substring(0, 1).toUpperCase()
					+ methodName.substring(1));
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			return null;
		}
		return method.invoke(owner);
	}

	@SuppressWarnings("unchecked")
	public  <T> T[]  getArrayType(){
		return (T[])new Object();
	}
	
	/**
	 * get class's all include classes ,and also include all the father class
	 * Only for class,not for interface
	 * 
	 * @param objectClazz
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static ArrayList<Class<?>> getClassesByClass(Class<?> objectClazz,
			Object o) {
		ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
		ArrayList<Class<?>> l = new ArrayList<Class<?>>();
		l.add(objectClazz);
		int i = 0;
		Class<?>[] classes = objectClazz.getDeclaredClasses();
		for (Class<?> clazz : classes) {
			checkForAdd(l, clazz);
		}
		Class<?>[] ifaces = objectClazz.getInterfaces();
		for (Class<?> clazz : ifaces) {
			checkForAdd(l, clazz);
		}
		Field[] field = objectClazz.getDeclaredFields();
		for (Field f : field) {
			Class<?> type = f.getType();
			if(type.isArray()){
				type.getClass();
			}
			Class<?>[] interfaces = type.getInterfaces();
			Class<?>[] cache=null;
			if (interfaces.length > 0||type.isInterface()) {
				if(type.isInterface()){
					cache=new Class<?>[interfaces.length+1];	
					for(int x=0;x<interfaces.length;x++){
						cache[x]=interfaces[x];
					}
					cache[interfaces.length]=type;
				}
				for (Class<?> clazz : cache==null?interfaces:cache) {
					try {
						if (clazz.equals(Map.class)) {
							ContextHolderFactory.getServiceContextHolder()
									.getContext().addObjectClasses(objectClazz);
							if (Modifier.isPrivate(f.getModifiers())) {
								Object ob = invokeMethod(o, f.getName(), null);
								Map<Object, Object> map1 = (Map<Object, Object>) ob;
								Iterator<Object> it = map1.keySet().iterator();
								while (it.hasNext()) {
									Object key = it.next();
									getClassesByClass(map1.get(key).getClass(),
											map1.get(key), allClasses);
								}
							} else {
								Map<Object, Object> map = (Map<Object, Object>) f
										.get(o);
								Iterator<Object> it = map.keySet().iterator();
								while (it.hasNext()) {
									Object key = it.next();
									getClassesByClass(map.get(key).getClass(),
											map.get(key), allClasses);
								}
							}
						} else if (clazz.equals(Set.class)) {
							ContextHolderFactory.getServiceContextHolder()
									.getContext().addObjectClasses(objectClazz);
							if (Modifier.isPrivate(f.getModifiers())) {
								Object ob = invokeMethod(o, f.getName(), null);
								Set<Object> set = (Set<Object>) ob;
								Iterator<Object> it = set.iterator();
								while (it.hasNext()) {
									Object value = it.next();
									getClassesByClass(value.getClass(), value,
											allClasses);
								}
							} else {
								Set<Object> set = (Set<Object>) f.get(o);
								Iterator<Object> it = set.iterator();
								while (it.hasNext()) {
									Object value = it.next();
									getClassesByClass(value.getClass(), value,
											allClasses);
								}
							}
						} else if (clazz.equals(List.class)) {
							ContextHolderFactory.getServiceContextHolder()
									.getContext().addObjectClasses(objectClazz);
							if (Modifier.isPrivate(f.getModifiers())) {
								Object ob = invokeMethod(o, f.getName(), null);
								List<Object> list = (List<Object>) ob;
								Iterator<Object> it = list.iterator();
								while (it.hasNext()) {
									Object value = it.next();
									getClassesByClass(value.getClass(), value,
											allClasses);
								}
							} else {
								List<Object> list = (List<Object>) f.get(o);
								Iterator<Object> it = list.iterator();
								while (it.hasNext()) {
									Object value = it.next();
									getClassesByClass(value.getClass(), value,
											allClasses);
								}
							}
						}
						checkForAdd(l, clazz);
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();

					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			} else {
				checkForAdd(l, type);
			}
		}

		while (i == 0) {
			i = 1;
			ArrayList<Class<?>> cachedList = new ArrayList<Class<?>>();
			for (int k = 0; k < l.size(); k++) {
				Class<?> c = l.get(k);
				if (allClasses.contains(c)) {
					if (isNormalClass(c)) {
						cachedList.add(c);
					} else {
						i = 0;
					}
					allClasses.add(c);
				} else {
					cachedList.add(c);
					allClasses.add(c);
				}
			}
			for (int k = 0; k < cachedList.size(); k++) {
				l.remove(cachedList.get(k));
			}
			if (l.size() > 0) {
				ArrayList<Class<?>> list = getClass(l);
				l.clear();
				l = list;
			}
		}
		return allClasses;
	}

	private static void checkForAdd(ArrayList<Class<?>> newClasses,
			Class<?> clazz) {
		if (!newClasses.contains(clazz)) {
			newClasses.add(clazz);
		}
	}

	@SuppressWarnings("unchecked")
	public static void getClassesByClass(Class<?> objectClazz, Object o,
			ArrayList<Class<?>> list) {
		ArrayList<Class<?>> l = new ArrayList<Class<?>>();
		l.add(objectClazz);
		int i = 0;
		// get this object's classes
		Class<?>[] classes = objectClazz.getClasses();
		for (Class<?> clazz : classes) {
			l.add(clazz);
		}
		Class<?>[] ifaces = objectClazz.getInterfaces();
		for (Class<?> clazz : ifaces) {
			checkForAdd(l, clazz);
		}
		// get the class all fields
		Field[] field = objectClazz.getDeclaredFields();
		for (Field f : field) {
			Class<?> type = f.getType();
			l.add(type);
			Class<?>[] interfaces = type.getInterfaces();
			for (Class<?> clazz : interfaces) {
				try {
					if (clazz.equals(Map.class)) {

						Map<Object, Object> map = (Map<Object, Object>) f
								.get(o);
						Iterator<Object> it = map.keySet().iterator();
						while (it.hasNext()) {
							Object key = it.next();
							getClassesByClass(map.get(key).getClass(),
									map.get(key), list);
						}
					} else if (clazz.equals(Set.class)) {
						Set<Object> set = (Set<Object>) f.get(o);
						Iterator<Object> it = set.iterator();
						while (it.hasNext()) {
							Object value = it.next();
							getClassesByClass(value.getClass(), value, list);
						}
					} else if (clazz.equals(List.class)) {
						List<Object> list1 = (List<Object>) f.get(o);
						Iterator<Object> it = list1.iterator();
						while (it.hasNext()) {
							Object value = it.next();
							getClassesByClass(value.getClass(), value, list);
						}
					}
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
					try {
						Object ob = invokeMethod(o, f.getName(), null);
						if (ob instanceof Map) {
							Map<Object, Object> map1 = (Map<Object, Object>) ob;
							Iterator<Object> it = map1.keySet().iterator();
							while (it.hasNext()) {
								Object key = it.next();
								getClassesByClass(map1.get(key).getClass(),
										map1.get(key));
							}
						} else if (ob instanceof Set) {
							Set<Object> set = (Set<Object>) ob;
							Iterator<Object> it = set.iterator();
							while (it.hasNext()) {
								Object value = it.next();
								getClassesByClass(value.getClass(), value);
							}
						} else if (ob instanceof List) {
							List<Object> list1 = (List<Object>) ob;
							Iterator<Object> it = list1.iterator();
							while (it.hasNext()) {
								Object value = it.next();
								getClassesByClass(value.getClass(), value);
							}
						}
					} catch (Exception e1) {
						e1.printStackTrace();
					}
				}
			}
		}
		ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
		while (i == 0) {
			i = 1;
			ArrayList<Class<?>> cachedList = new ArrayList<Class<?>>();
			for (int k = 0; k < l.size(); k++) {
				Class<?> c = l.get(k);
				if (!allClasses.contains(c)) {
					if (isNormalClass(c)) {
						cachedList.add(c);
					} else {
						i = 0;
					}
					allClasses.add(c);
				} else {
					cachedList.add(c);
				}
			}
			for (int k = 0; k < cachedList.size(); k++) {
				l.remove(cachedList.get(k));
			}
			if (l.size() > 0) {
				ArrayList<Class<?>> list1 = getClass(l);
				l.clear();
				l = list1;
				i = 0;
			}
		}
		allClasses.add(objectClazz);
		list.addAll(allClasses);
	}

	private static ArrayList<Class<?>> getClass(ArrayList<Class<?>> list) {
		ArrayList<Class<?>> newClasses = new ArrayList<Class<?>>();
		for (int k = 0; k < list.size(); k++) {
			Class<?> c = list.get(k);
			Class<?>[] classes = c.getClasses();
			for (Class<?> clazz : classes) {
				checkForAdd(newClasses, clazz);
			}
			Field[] fields = c.getDeclaredFields();
			for (Field f : fields) {
				checkForAdd(newClasses, f.getType());
			}
		}
		return newClasses;
	}

	static {
		baseDataType.put("int", int.class);
		baseDataType.put("double", double.class);
		baseDataType.put("long", long.class);
		baseDataType.put("boolean", boolean.class);
		baseDataType.put("float", float.class);
		baseDataType.put("char", char.class);
		baseDataType.put("byte", byte.class);
		baseDataType.put("short", short.class);
	}

	private static boolean isNormalClass(Class<?> c) {
		if ((c.getName().substring(0, 3)).equals("jav")
				|| baseDataType.get(c.getName()) != null) {
			return true;
		}
		return false;
	}
}
